#ifndef __C_SOCKET_LOW_INCLUDED__
#define __C_SOCKET_LOW_INCLUDED__

#include "CSocketLow.h"
#include "ISocket.h"
#include "os.h"

#include "IrrCompileConfig.h"

#ifdef _IRR_PSP_
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include <pspthreadman.h>
#include <pspkernel.h>
#include <pspnet.h>
#include <pspnet_inet.h>
#include <pspnet_resolver.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/select.h>
#include <netdb.h>
#include <sys/socket.h>
#endif

namespace game
{
	namespace net
	{
		// -- sockets --

		CSocketLow::~CSocketLow()
		{
#ifdef _IRR_WINDOWS_
			if(thid)
			{
				delete thid;
			}
#else

#endif
			if (Connected == true)
			{
#ifdef _IRR_PSP_
				close(Socket);
#endif
			}

			NetworkDriver->removeSocket(this);
		}

		CSocketLow::CSocketLow()
			: CatchEvent(true), Connected(false), error(IRR_NO_ERROR)
		{

		}

		void CSocketLow::setInfo(game::net::INetworkDriver* networkDriver, int socketIndex)
		{
			NetworkDriver = networkDriver;
			SocketIndex = socketIndex;
		}

		irr::core::stringc& CSocketLow::getRemoteIP()
		{
			return rem_ip;
		}

		irr::core::stringc& CSocketLow::getRemoteHost()
		{
			return rem_host;
		}

		E_SOCK_ERROR_CODES CSocketLow::getError() 
		{
			return error;
		}

		bool CSocketLow::getConnectError() 
		{
			return ConnectError;
		}

		bool CSocketLow::getCatchEvent() 
		{
			return CatchEvent;
		}

		bool CSocketLow::getConnected() 
		{
			return Connected;
		}

		void CSocketLow::unsetCatchEvent()  
		{
			CatchEvent = false;
		}

		void CSocketLow::connectTo(c8* HostName, int Port)
		{
#ifdef _IRR_PSP_
			static int address;

			ConnectionTimeout= irr::os::Timer::getRealTime();

			PerformingConnection = true;

			error = UNKNOWN_ERROR;

			if ( NetworkDriver->getConnectionInitialized() == false )
			{
				return;
			}

			thid = sceKernelCreateThread("connection_thread", nd_ConnectionThread,  0x18, 0x10000, PSP_THREAD_ATTR_USER, NULL);

			if (Connected == true)
			{
				error = INVALID_SOCKET_STATE;
				NetworkDriver->getNetworkCallback()->onError(this);
				return;
			}
			strncpy(szHostName, HostName, 128);
			lPort = Port;

			if (thid >= 0)
			{

				address = (int)this;

				if (sceKernelStartThread(thid, 4, &address))
				{
					sceKernelDeleteThread(thid);
					error = UNKNOWN_ERROR;
					NetworkDriver->getNetworkCallback()->onError(this);
				}

			}
			else
			{
				error = UNKNOWN_ERROR;
				NetworkDriver->getNetworkCallback()->onError(this);

			}

			return;
#else
			static int address;

			ConnectionTimeout= irr::os::Timer::getRealTime();

			PerformingConnection = true;

			error = UNKNOWN_ERROR;

		/*	if ( NetworkDriver->getConnectionInitialized() == false )
			{
				return;
			}  */


			//	int thid = sceKernelCreateThread("connection_thread", nd_ConnectionThread,  0x18, 0x10000, PSP_THREAD_ATTR_USER, NULL);
			irrThread* thid = NULL;
			irrThread::delegateThreadCallback* callback = new irrThread::delegateThreadCallback;
			*callback += NewDelegate(this, &CSocketLow::nd_ConnectionThread);
			
			if(!callback->IsNull())
			{
				thid = new irrThread(ETP_ABOVE_LOW, callback, "nd_ConnectionThread", (void*)"");
			}

			if (Connected == true)
			{
				error = INVALID_SOCKET_STATE;
				NetworkDriver->getNetworkCallback()->onError(this);
				return;
			}
			strncpy(szHostName, HostName, 128);
			lPort = Port;

			if (thid)
			{

				address = (int)this;
				thid->start((void*)&address);
				if (0)
				{
					//sceKernelDeleteThread(thid);
					delete  thid;
					error = UNKNOWN_ERROR;
					NetworkDriver->getNetworkCallback()->onError(this);
				}

			}
			else
			{
				error = UNKNOWN_ERROR;
				NetworkDriver->getNetworkCallback()->onError(this);

			}

			return;
#endif
		}
#ifdef _IRR_WINDOWS_
		void CSocketLow::nd_ConnectionThread(void *argv)
		{

			int *p = (int*)argv;
			CSocketLow *origin = (CSocketLow*)(*p) ;

			struct hostent *data;
			struct sockaddr_in saddr;

			data = gethostbyname(origin->szHostName);
			if (data == 0)
			{
				origin->error =CANNOT_RESOLVE_HOSTNAME;
				origin->ConnectError = true;
				origin->Connected=false;
				origin->CatchEvent = true;
				TerminateThread(GetCurrentThread(), 0);

				//	sceKernelExitDeleteThread(0);
			}

			origin->Socket = socket(AF_INET, SOCK_STREAM, 0);
			if (origin->Socket < 0)
			{
				origin->ConnectError = true;
				origin->Connected=false;
				origin->CatchEvent = true;
				origin->error = UNKNOWN_ERROR;

				TerminateThread(GetCurrentThread(), 0);
				//sceKernelExitDeleteThread(0);
			}
			saddr.sin_family = AF_INET;    // host byte order
			saddr.sin_port = htons(origin->lPort);  // short, network byte order
			saddr.sin_addr = *((struct in_addr *)data->h_addr);

			origin->getBufferIn()->clear();
			origin->getBufferOut()->clear();

			if (connect(origin->Socket, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) {


				origin->error = CONNECTION_REJECTED;

				origin->ConnectError = true;
				origin->Connected=false;
				origin->CatchEvent = true;

				TerminateThread(GetCurrentThread(), 0);
				//sceKernelExitDeleteThread(0);
			}



			origin->rem_host = origin->szHostName;
			origin->rem_ip = inet_ntoa( saddr.sin_addr );

			origin->ConnectError = false;
			origin->Connected=true;
			origin->CatchEvent = true;

			//	return 0;


		}
#endif
		int nd_ConnectionThread(unsigned int argc, void *argv)
		{
#ifdef _IRR_PSP_
			int *p = (int*)argv;
			CSocketLow *origin = (CSocketLow*)(*p) ;

			struct hostent *data;
			struct sockaddr_in saddr;

			data = gethostbyname(origin->szHostName);
			if (data == 0)
			{

				origin->error = CANNOT_RESOLVE_HOSTNAME;
				origin->ConnectError = true;
				origin->Connected=false;
				origin->CatchEvent = true;

				sceKernelExitDeleteThread(0);
			}

			origin->Socket = socket(AF_INET, SOCK_STREAM, 0);
			if (origin->Socket < 0)
			{
				origin->ConnectError = true;
				origin->Connected=false;
				origin->CatchEvent = true;
				origin->error = UNKNOWN_ERROR;
				sceKernelExitDeleteThread(0);
			}
			saddr.sin_family = AF_INET;    // host byte order
			saddr.sin_port = htons(origin->lPort);  // short, network byte order
			saddr.sin_addr = *((struct in_addr *)data->h_addr);

			origin->getBufferIn()->clear();
			origin->getBufferOut()->clear();

			if (connect(origin->Socket, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) {


				origin->error = CONNECTION_REJECTED;

				origin->ConnectError = true;
				origin->Connected=false;
				origin->CatchEvent = true;

				sceKernelExitDeleteThread(0);
			}



			origin->rem_host = origin->szHostName;
			origin->rem_ip = inet_ntoa( saddr.sin_addr );

			origin->ConnectError = false;
			origin->Connected=true;
			origin->CatchEvent = true;

			return 0;
#else
			return 0;
#endif

		}

		bool CSocketLow::isPerformingConn()
		{
			return PerformingConnection;
		}
		void CSocketLow::unsetPerform()
		{
			PerformingConnection=false;
		}

		int CSocketLow::getConnTimeout()
		{
			return ConnectionTimeout;
		}
		void CSocketLow::setConnTimeout(int c)
		{
			ConnectionTimeout=c;
		}


		void CSocketLow::closeSocket()
		{
			if (Connected == true)
			{
#ifdef _IRR_PSP_
				close(Socket);
				Connected = false;
				BufferIn.clear();
				BufferOut.clear();
#else
				closesocket (Socket);
				Connected = false;
				BufferIn.clear();
				BufferOut.clear();
#endif
			}
		}

		void CSocketLow::write(c8* data)
		{
			if (Connected)
			{
				BufferOut.append_data(data, strlen(data));
			}
		}

		void CSocketLow::writeLine(c8* data)
		{
			if (Connected == false)
				return;

			BufferOut.append_data(data, strlen(data));
			BufferOut.append_data("\r\n", 2);
		}

		void CSocketLow::writeBytes(c8* data, int size)
		{
			if (Connected )
			{
				BufferOut.append_data( data, size );
			}
		}
		void CSocketLow::writeBytes(irr::core::stringb& data)
		{
			if (Connected)
			{
				BufferOut.append_data( data.ret_buffer(), data.get_size() );
			}	 
		}

		void CSocketLow::writeBytes(irr::core::stringb* data)
		{
			if (Connected)
			{
				BufferOut.append_data( data->ret_buffer(), data->get_size() );
			}
		}

		bool CSocketLow::getData(irr::core::stringb* buffer)
		{
			bool result = false;

			if (Connected == false)
			{
				buffer->clear();
				buffer->set_pointer( BufferIn.ret_buffer(), BufferIn.get_size() );
				BufferIn.set_pointer(0, 0);

				result = true;
			}

			return result;
		}

		bool CSocketLow::readLine(irr::core::stringc* buffer)
		{
			if (Connected == false)
				return false;

			int count = 0;

			if ( (count = BufferIn.substr_count("\n")) == 0)
				return false;

			irr::core::array<irr::core::stringb>& arr = BufferIn.explode("\n",2);

			irr::core::stringb readl = arr.pointer()[0];
			c8 *p;
			p = readl.ret_buffer();
			if (p[ readl.get_size() - 1] == '\r')
			{
				readl.set_pointer(p, readl.get_size() - 1);
			}
			readl.append_zero();


			*buffer = readl.to_stringc()->c_str();

			BufferIn.clear();

			if (arr.size() != 1)
			{
				BufferIn = arr.pointer()[1];
			}

			return true;
		}

		bool CSocketLow::isConnected()
		{
			return Connected;
		}


		int CSocketLow::getSocketId()
		{
			return Socket;
		}

		int CSocketLow::getIndex()
		{
			return SocketIndex;
		}

		irr::core::stringb* CSocketLow::getBufferIn()
		{
			return &BufferIn;
		}

		irr::core::stringb* CSocketLow::getBufferOut()
		{
			return &BufferOut;
		}
	}
}
#endif
